/*
Minims .Net Playstation 3 UPNP ContentDirectory
Copyright (C) 2008  Guillaume Plourde

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; version 2
of the License.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

using System;
using System.Collections.Generic;
using System.Text;

using System.Xml;
using System.Threading;
using System.Net;
using System.Net.Sockets;
using System.IO;
using System.Collections;

namespace minims
{
	public class Client
	{
		public string address;
		public string name;
		public string manufacturer;
		public bool access;
	}
	public class HTTP_SERVER
	{
		private bool Started;
		private int Port;

		private FileManager filemanager;
		private Hashtable services;
		private Thread HttpdThread;
		private List<Client> clients;

		public HTTP_SERVER(Hashtable servs, FileManager _fm, int p)
		{
			this.filemanager = _fm;
			this.services = servs;
			this.Port = p;

			clients = new List<Client>();
		}
		HttpListener listener;

		private bool CheckClientAccess(string ip)
		{
			bool found = false;

			// Check is device is valid.
			foreach (Client client in clients)
			{
				if (client.address == ip)
					found = true;
			}
			// Add the device if not found
			if (!found)
			{
				Client c = new Client();
				c.address = ip;
				c.access = true;
				clients.Add(c);
			}

			foreach (Client client in clients)
			{
				if (client.address == ip && client.access)
					return true;
			}

			//AccessUpdatedEvent();
			return false;
		}
		private void HttpdListen()
		{
			Console.WriteLine("HTTP Server listening on port: {0}", ConfigManager.GetPort);

			while (Started)
			{
				try
				{
					HttpListenerContext context = listener.GetContext();
					HttpListenerRequest request = context.Request;

					Console.WriteLine("HTTP/{0} {1}", request.ProtocolVersion, request.RawUrl);
					for (int i = 0; i < request.Headers.Count; i++)
						Console.WriteLine("{0}: {1}", request.Headers.GetKey(i), request.Headers.Get(i));

					if (CheckClientAccess(request.RemoteEndPoint.Address.ToString()))
						http_respond(context, request);
				}
				catch (SystemException e)
				{
					Console.WriteLine(e);
				}
			}
		}
		public void Start()
		{
			Started = true;
			listener = new HttpListener();
			listener.Prefixes.Add("http://*:" + ConfigManager.GetPort + "/");
			listener.Start();
			HttpdThread = new Thread(new ThreadStart(HttpdListen));
			HttpdThread.Start();
		}
		public void Stop()
		{
			Started = false;
			listener.Stop();
			listener.Abort();
		}
		private long[] GetRange(string sz)
		{
			long[] result = new long[2];

			sz = sz.Replace("bytes=", "");

			char[] sep = { '-' };
			string[] tokens = sz.Split(sep);
			result[0] = long.Parse(tokens[0]);
			result[1] = long.Parse(tokens[1]);
			return result;
		}
		private void Send(Stream inStream, HttpListenerResponse response, long start, long end)
		{
			try
			{
				response.ProtocolVersion = new Version("1.1");
				response.KeepAlive = true;
				response.ContentLength64 = inStream.Length;
				response.AddHeader("Accept-Ranges", "bytes");

				using (Stream outStream = response.OutputStream)
				{
					Byte[] buffer = new Byte[12000];
					int count = 0;

					inStream.Position = start;

					Console.WriteLine("Send bytes: {0} to {1} of {2}", start, end, inStream.Length);
					while (inStream.Position < end)
					{
						count = inStream.Read(buffer, 0, buffer.Length);
						outStream.Write(buffer, 0, count);
						Console.Write(".");
					}
					Console.WriteLine();
					outStream.Flush();
					outStream.Close();
				}
				response.Close();
			}
			catch (Exception exp) { Console.WriteLine(exp.Message); }
		}
		private void http_respond(HttpListenerContext context, HttpListenerRequest request)
		{
			HttpListenerResponse response = context.Response;

			if (request.HttpMethod == "GET")
			{
				if (request.Url.Segments[1] == "System/")
				{
					Console.WriteLine("System information request from: {0}", request.RemoteEndPoint.Address.ToString());
					string sysFile = filemanager.GetFile(request.RawUrl);
					MemoryStream ms = new MemoryStream();
					BinaryWriter bw = new BinaryWriter(ms);
					bw.Write(Encoding.UTF8.GetBytes(sysFile));
					Send(ms, response, 0, ms.Length);
					bw.Close();
					ms.Close();
				}

				else if (request.Url.Segments[1] == "Media/")
				{
					Console.WriteLine("Media request.");
					int cid = int.Parse(request.Url.Segments[2].Replace("/", ""));
					ContentDirectory cds = ((ContentDirectory)services["ContentDirectory"]);
					Container container = cds.FindContainer(cds.root, cid);
					FileInfo fi = null;

					foreach (CDS_ITEM item in container.items)
					{
						if (item.classname == "media" && ((MediaItem)item).id.ToString() == request.Url.Segments[3])
						{
							Console.WriteLine(((MediaItem)item).filename);
							fi = new FileInfo(((MediaItem)item).filename);
						}
					}

					if (fi.Exists)
					{
						FileStream inStream = new FileStream(fi.FullName, FileMode.Open, FileAccess.Read);

						long[] range = { 0, inStream.Length };

						if (request.Headers.Get("Range") != null)
							range = GetRange(request.Headers.Get("Range"));

						Send(inStream, response, range[0], range[1]);
					}
				}
			}
			else if (request.HttpMethod == "POST")
			{
				StringBuilder sb = new StringBuilder();

				XmlDocument querybody;
				String schemas;
				String query;

				ParseSoapAction(request.Headers.Get("SOAPACTION"), out schemas, out query);

				StreamReader sr = new StreamReader(request.InputStream);

				querybody = new XmlDocument();
				querybody.LoadXml(sr.ReadToEnd());

				XmlNamespaceManager nsmgr = new XmlNamespaceManager(querybody.NameTable);
				nsmgr.AddNamespace("s", "http://schemas.xmlsoap.org/soap/envelope/");
				nsmgr.AddNamespace("u", schemas);

				if (query.Contains("GetSearchCapabilities"))
					sb.Append(((ContentDirectory)services["ContentDirectory"]).GetSearchCapabilities(schemas));
				else if (query.Contains("GetSortCapabilities"))
					sb.Append(((ContentDirectory)services["ContentDirectory"]).GetSortCapabilities(schemas));
				else if (query.Contains("GetSystemUpdateID"))
					sb.Append(((ContentDirectory)services["ContentDirectory"]).GetSystemUpdateID(schemas));
				else if (query.Contains("Browse"))
				{
					int ObjectID = int.Parse(querybody.SelectSingleNode("/s:Envelope/s:Body/u:Browse/ObjectID", nsmgr).InnerText);
					String BrowserFlag = querybody.SelectSingleNode("/s:Envelope/s:Body/u:Browse/BrowseFlag", nsmgr).InnerText;
					String Filter = querybody.SelectSingleNode("/s:Envelope/s:Body/u:Browse/Filter", nsmgr).InnerText;
					int StartingIndex = int.Parse(querybody.SelectSingleNode("/s:Envelope/s:Body/u:Browse/StartingIndex", nsmgr).InnerText);
					int RequestedCount = int.Parse(querybody.SelectSingleNode("/s:Envelope/s:Body/u:Browse/RequestedCount", nsmgr).InnerText);
					String SortCriteria = querybody.SelectSingleNode("/s:Envelope/s:Body/u:Browse/SortCriteria", nsmgr).InnerText;
					Console.WriteLine("{0} {1} {2} {3} {4} {5} ", ObjectID, BrowserFlag, Filter, StartingIndex, RequestedCount, SortCriteria);
					sb.Append(((ContentDirectory)services["ContentDirectory"]).Browse(ObjectID, BrowserFlag, Filter, StartingIndex, RequestedCount, SortCriteria, schemas));
				}

				response = context.Response;
				response.ContentType = " text/xml";

				MemoryStream ms = new MemoryStream();
				BinaryWriter bw = new BinaryWriter(ms);
				bw.Write(Encoding.UTF8.GetBytes(sb.ToString()));
				Send(ms, response, 0, ms.Length);
				bw.Close();
				ms.Close();
				Console.WriteLine("Post done.");
			}
		}
		static void ParseSoapAction(String raw, out String schemas, out String query)
		{
			raw = raw.Replace("\"", "");
			string[] tokens = raw.Split(new char[] { '#' });
			schemas = tokens[0];
			query = tokens[1];
		}
		static public void OpenReadFile(string file, out byte[] buffer)
		{
			long length;
			FileStream f;

			f = new FileStream(file, FileMode.Open, FileAccess.Read);
			length = f.Length;

			buffer = new byte[length];
			f.Read(buffer, 0, (int)length);
			f.Close();
		}
	}
}